Objetivo

Predecir el consumo para los 3 meses siguiente dada una serie de datos del consumo previo junto a variables exogenas. Las variables next_consume, next_2_consume y next_3_consume son las variables dependientes que queremos predecir.

Cargar Bibliotecas

install.packages("readr")
Error in install.packages : Updating loaded packages
install.packages("ranger")
Error in install.packages : Updating loaded packages
install.packages("dplyr")
Error in install.packages : Updating loaded packages
install.packages("skimr")
Error in install.packages : Updating loaded packages
install.packages("caret")
Error in install.packages : Updating loaded packages
library(readr) # para leeer el dataset
library(ranger) # random forest con esteroides
library(dplyr) # para manipular datos
library(skimr) # para mirar los datos
library(caret) # framework de machine learning

Cargar el dataset

# dataset %>% select(Date)
# dataset %>% names() %>% as.data.frame()
 skimr::skim(dataset)# %>% knitr::kable() %>% kable_styling(font_size = 9)
── Data Summary ────────────────────────
                           Values 
Name                       dataset
Number of rows             532    
Number of columns          409    
_______________________           
Column type frequency:            
  numeric                  409    
________________________          
Group variables            None   

La Metodologia


dataset <- dataset %>% tidyr::drop_na()
train<-dataset %>% sample_frac(0.8)
test <-setdiff(dataset,train)
train
test

Modelo para predecir next_consume

rf_model1 <- ranger(tmg ~ . ,data=train, splitrule = "maxstat")
rf_model1
Ranger result

Call:
 ranger(tmg ~ ., data = train, splitrule = "maxstat") 

Type:                             Regression 
Number of trees:                  500 
Sample size:                      426 
Number of independent variables:  408 
Mtry:                             20 
Target node size:                 5 
Variable importance mode:         none 
Splitrule:                        maxstat 
OOB prediction error (MSE):       0.001165064 
R squared (OOB):                  0.8066949 
testThing <- function(splitrule) {
  rf_model1 <- ranger(tmg ~ . ,data=train, splitrule = splitrule)
  predictions_test <- predict(rf_model1, data = test)$predictions
  RMSE_test <- sqrt(mean((predictions_test - test$tmg)^2))
  predictions_train <- predict(rf_model1, data = train)$predictions
  RMSE_train <- sqrt(mean((predictions_train - train$tmg)^2))
  return(c(RMSE_train, RMSE_test))
}
testNTimes <- function(splitrule, n) {
  results <- matrix(0, n, 2)
  for (i in 1:n) {
    result <- testThing(splitrule)
    results[i, ] <- result
  }
  avg_RMSE_train <- mean(results[, 1])
  avg_RMSE_test <- mean(results[, 2])
  return(c(avg_RMSE_train, avg_RMSE_test))
}
testNTimes("variance", 100)
[1] 0.01528344 0.03396292
testNTimes("extratrees", 100)
[1] 0.01861879 0.03461950
testNTimes("maxstat", 100)
[1] 0.02263960 0.03593478
# rf_model1$prediction.error
predictions <- predict(rf_model1, data = test)$predictions
# Compute the RMSE (Root Mean Square Error)
RMSE <- sqrt(mean((predictions - test$tmg)^2))

print(rf_model1$prediction.error)
print(RMSE)

Entrenamiento

Out Of Box Sampling.

Los errores MSE y R squared se calculan sobre el OOB. El concepto de OOB está relacionado con el proceso de bootstrapping, que es una técnica de muestreo utilizada en la construcción de los árboles de decisión en Random Forest. En bootstrapping, se extrae una muestra aleatoria de los datos de entrenamiento con reemplazo, lo que significa que algunas instancias pueden ser elegidas varias veces, mientras que otras pueden no ser elegidas en absoluto.

Importancia de las variables

rf_model1 <- ranger(tmg ~ . ,data=train, importance = "impurity")
rf_model1

impurity: Este es el método predeterminado, que calcula la importancia de una característica basándose en la disminución de la impureza del nodo (por ejemplo, Gini o entropía) cuando una característica se utiliza para dividir en los árboles de decisión. Cuanto mayor sea la disminución de la impureza, más importante se considera la característica.


rf_model1$variable.importance
data.frame(impurity=rf_model1$variable.importance) %>% arrange(desc(impurity))
rf_model1 <- ranger(tmg ~ pec_28 + pec_27 + pec_32 + pec_62 + psd12_58 + psd12_50 + psd11_58 + pec_51 + psd11_50 + pec_29,data=train, importance = "impurity")
rf_model1

Prediccion

predictions1 <- predict(rf_model1,data = test, type='response')
predictions1$predictions %>% as.data.frame

Error MSE en test

mse<-function(act,pred) {mean((act- pred)^2)}

data.frame(pred=predictions1$predictions, act=test$tmg) %>% summarise(mse=mse(act,pred))

Intervalos de prediccion (quantile regression)

En vez de utilizar el promedio se utilzan los cuantiles para tener un intervalo de predicción (Meinshausen, 2006). A la hora de realizar el split, en vez de utilizar MSE o alguna otra metrica de impureza, se utiliza una metrica que tiene en cuenta a los cuantiles . Luego en cada hoja en vez de calcular el promedio, se calculan cuantiles.

rf_model1 <- ranger(tmg ~ . ,data=train, importance = "impurity",quantreg = TRUE)
rf_model1
predictions1 <- predict(rf_model1,data = test, type= "quantiles")
predictions1$predictions %>% as.data.frame()
p1<-data.frame(predictions1$predictions,act=test$tmg,label="Mag prediction")

p1  %>% ggplot()+
  geom_point(aes(x=act,y=quantile..0.5),color='red')+
  geom_errorbar(aes(x=act,y=quantile..0.5,ymax=quantile..0.9,ymin=quantile..0.1),color='orange')+
  theme_classic()

Intervalo de confianza

Similar al intervalo de prediccion. En las implementaciones de Random Forests, suele confudirse. En terminos generales, uno se aplica sobre una observacion/predicción en general, mientras que el otro trata sobre estadisticos. Un ejemplo seria, la diferencia entre desviacion estandar de una variable y el error estandar sobre un conjunto de muestras.

If we change the training dataset just a little bit, will Random Forest give you the same result for that particular example?

(Wagner et al. 2014) Basado en una tecnica que se llama jacknife.

rf_model1 <- ranger(tmg ~ ., data=train, importance = "impurity",keep.inbag  = TRUE)
rf_model1
predictions1 <- predict(rf_model1,data = train, type= "se")

predictions1$predictions %>% as.data.frame()
predictions1$se %>% as.data.frame()

data.frame(pred=predictions1$predictions, se= predictions1$se, act=train$tmg) %>% ggplot()+
  geom_point(aes(x=act,y=pred),color='red')+
  geom_errorbar(aes(x=act,y=pred,ymax=pred+se,ymin=pred-se),color='orange')+
  theme_classic()
LS0tCnRpdGxlOiAiUmFuZG9tIEZvcmVzdHM6IFNpcnZlIHBhcmEgdG9kbz8iCm91dHB1dDogaHRtbF9ub3RlYm9vawpkYXRlOiAnMjAyMy0wNC0yNycKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCiMgT2JqZXRpdm8KClByZWRlY2lyIGVsIGNvbnN1bW8gcGFyYSBsb3MgMyBtZXNlcyBzaWd1aWVudGUgZGFkYSB1bmEgc2VyaWUgZGUgZGF0b3MgZGVsIGNvbnN1bW8gcHJldmlvIGp1bnRvIGEgdmFyaWFibGVzIGV4b2dlbmFzLiBMYXMgdmFyaWFibGVzIGBuZXh0X2NvbnN1bWVgLCBgbmV4dF8yX2NvbnN1bWVgIHkgYG5leHRfM19jb25zdW1lYCBzb24gbGFzIHZhcmlhYmxlcyBkZXBlbmRpZW50ZXMgcXVlIHF1ZXJlbW9zIHByZWRlY2lyLgoKIyMgQ2FyZ2FyIEJpYmxpb3RlY2FzCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQppbnN0YWxsLnBhY2thZ2VzKCJyZWFkciIpCmluc3RhbGwucGFja2FnZXMoInJhbmdlciIpCmluc3RhbGwucGFja2FnZXMoImRwbHlyIikKaW5zdGFsbC5wYWNrYWdlcygic2tpbXIiKQppbnN0YWxsLnBhY2thZ2VzKCJjYXJldCIpCmxpYnJhcnkocmVhZHIpICMgcGFyYSBsZWVlciBlbCBkYXRhc2V0CmxpYnJhcnkocmFuZ2VyKSAjIHJhbmRvbSBmb3Jlc3QgY29uIGVzdGVyb2lkZXMKbGlicmFyeShkcGx5cikgIyBwYXJhIG1hbmlwdWxhciBkYXRvcwpsaWJyYXJ5KHNraW1yKSAjIHBhcmEgbWlyYXIgbG9zIGRhdG9zCmxpYnJhcnkoY2FyZXQpICMgZnJhbWV3b3JrIGRlIG1hY2hpbmUgbGVhcm5pbmcKYGBgCgojIyBDYXJnYXIgZWwgZGF0YXNldAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KZGF0YXNldCA8LSByZWFkcjo6cmVhZF9jc3YoImRhdGFzZXQuY3N2IikKc2VsZWN0ZWRfY29sdW1ucyA8LSBkYXRhc2V0WywgZ3JlcGwoInBzZChbMC05XXsyfSk/Xyg1MHw1OCl8Y29vcmRjLip8cGVjLip8dG1nIiwgbmFtZXMoZGF0YXNldCkpXQpkYXRhc2V0IDwtIHNlbGVjdGVkX2NvbHVtbnMKbmFtZXMoZGF0YXNldCkKYGBgCgpgYGB7cn0KIyBkYXRhc2V0ICU+JSBzZWxlY3QoRGF0ZSkKIyBkYXRhc2V0ICU+JSBuYW1lcygpICU+JSBhcy5kYXRhLmZyYW1lKCkKYGBgCgpgYGB7cn0KIHNraW1yOjpza2ltKGRhdGFzZXQpIyAlPiUga25pdHI6OmthYmxlKCkgJT4lIGthYmxlX3N0eWxpbmcoZm9udF9zaXplID0gOSkKYGBgCgojIyBMYSBNZXRvZG9sb2dpYQoKIVtdKGh0dHBzOi8vaGFycG9tYXh4LmdpdGh1Yi5pby9wb3N0LzIwMjAtMDktMDktZXhwZXJpbWVudGFsLWRlc2lnbi5lbl9maWxlcy9tbC1leHBlcmltZW50YS1kZXNpZ25BLnBuZyl7c3R5bGU9ImNvbG9yOndoaXRlIn0KCmBgYHtyfQoKZGF0YXNldCA8LSBkYXRhc2V0ICU+JSB0aWR5cjo6ZHJvcF9uYSgpCnRyYWluPC1kYXRhc2V0ICU+JSBzYW1wbGVfZnJhYygwLjgpCnRlc3QgPC1zZXRkaWZmKGRhdGFzZXQsdHJhaW4pCnRyYWluCnRlc3QKYGBgCgojIyBNb2RlbG8gcGFyYSBwcmVkZWNpciBuZXh0X2NvbnN1bWUKCmBgYHtyfQpyZl9tb2RlbDEgPC0gcmFuZ2VyKHRtZyB+IC4gLGRhdGE9dHJhaW4sIHNwbGl0cnVsZSA9ICJtYXhzdGF0IikKcmZfbW9kZWwxCmBgYApgYGB7cn0KdGVzdFRoaW5nIDwtIGZ1bmN0aW9uKHNwbGl0cnVsZSkgewogIHJmX21vZGVsMSA8LSByYW5nZXIodG1nIH4gLiAsZGF0YT10cmFpbiwgc3BsaXRydWxlID0gc3BsaXRydWxlKQogIHByZWRpY3Rpb25zX3Rlc3QgPC0gcHJlZGljdChyZl9tb2RlbDEsIGRhdGEgPSB0ZXN0KSRwcmVkaWN0aW9ucwogIFJNU0VfdGVzdCA8LSBzcXJ0KG1lYW4oKHByZWRpY3Rpb25zX3Rlc3QgLSB0ZXN0JHRtZyleMikpCiAgcHJlZGljdGlvbnNfdHJhaW4gPC0gcHJlZGljdChyZl9tb2RlbDEsIGRhdGEgPSB0cmFpbikkcHJlZGljdGlvbnMKICBSTVNFX3RyYWluIDwtIHNxcnQobWVhbigocHJlZGljdGlvbnNfdHJhaW4gLSB0cmFpbiR0bWcpXjIpKQogIHJldHVybihjKFJNU0VfdHJhaW4sIFJNU0VfdGVzdCkpCn0KdGVzdE5UaW1lcyA8LSBmdW5jdGlvbihzcGxpdHJ1bGUsIG4pIHsKICByZXN1bHRzIDwtIG1hdHJpeCgwLCBuLCAyKQogIGZvciAoaSBpbiAxOm4pIHsKICAgIHJlc3VsdCA8LSB0ZXN0VGhpbmcoc3BsaXRydWxlKQogICAgcmVzdWx0c1tpLCBdIDwtIHJlc3VsdAogIH0KICBhdmdfUk1TRV90cmFpbiA8LSBtZWFuKHJlc3VsdHNbLCAxXSkKICBhdmdfUk1TRV90ZXN0IDwtIG1lYW4ocmVzdWx0c1ssIDJdKQogIHJldHVybihjKGF2Z19STVNFX3RyYWluLCBhdmdfUk1TRV90ZXN0KSkKfQp0ZXN0TlRpbWVzKCJ2YXJpYW5jZSIsIDEwMCkKdGVzdE5UaW1lcygiZXh0cmF0cmVlcyIsIDEwMCkKdGVzdE5UaW1lcygibWF4c3RhdCIsIDEwMCkKYGBgCgpgYGB7cn0KIyByZl9tb2RlbDEkcHJlZGljdGlvbi5lcnJvcgpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHJmX21vZGVsMSwgZGF0YSA9IHRlc3QpJHByZWRpY3Rpb25zCiMgQ29tcHV0ZSB0aGUgUk1TRSAoUm9vdCBNZWFuIFNxdWFyZSBFcnJvcikKUk1TRSA8LSBzcXJ0KG1lYW4oKHByZWRpY3Rpb25zIC0gdGVzdCR0bWcpXjIpKQoKcHJpbnQocmZfbW9kZWwxJHByZWRpY3Rpb24uZXJyb3IpCnByaW50KFJNU0UpCmBgYAoKIyMjIEVudHJlbmFtaWVudG8KCiMjIyMgT3V0IE9mIEJveCBTYW1wbGluZy4KCkxvcyBlcnJvcmVzIE1TRSB5IFIgc3F1YXJlZCBzZSBjYWxjdWxhbiBzb2JyZSBlbCBPT0IuIEVsIGNvbmNlcHRvIGRlICoqT09CKiogZXN0w6EgcmVsYWNpb25hZG8gY29uIGVsIHByb2Nlc28gZGUgKipib290c3RyYXBwaW5nKiosIHF1ZSBlcyB1bmEgdMOpY25pY2EgZGUgbXVlc3RyZW8gdXRpbGl6YWRhIGVuIGxhIGNvbnN0cnVjY2nDs24gZGUgbG9zIMOhcmJvbGVzIGRlIGRlY2lzacOzbiBlbiBSYW5kb20gRm9yZXN0LiBFbiBib290c3RyYXBwaW5nLCBzZSBleHRyYWUgdW5hIG11ZXN0cmEgYWxlYXRvcmlhIGRlIGxvcyBkYXRvcyBkZSBlbnRyZW5hbWllbnRvIGNvbiByZWVtcGxhem8sIGxvIHF1ZSBzaWduaWZpY2EgcXVlIGFsZ3VuYXMgaW5zdGFuY2lhcyBwdWVkZW4gc2VyIGVsZWdpZGFzIHZhcmlhcyB2ZWNlcywgbWllbnRyYXMgcXVlIG90cmFzIHB1ZWRlbiBubyBzZXIgZWxlZ2lkYXMgZW4gYWJzb2x1dG8uXApcCgojIyMjIEltcG9ydGFuY2lhIGRlIGxhcyB2YXJpYWJsZXMKCmBgYHtyfQpyZl9tb2RlbDEgPC0gcmFuZ2VyKHRtZyB+IC4gLGRhdGE9dHJhaW4sIGltcG9ydGFuY2UgPSAiaW1wdXJpdHkiKQpyZl9tb2RlbDEKYGBgCgoqKmltcHVyaXR5Kio6IEVzdGUgZXMgZWwgbcOpdG9kbyBwcmVkZXRlcm1pbmFkbywgcXVlIGNhbGN1bGEgbGEgaW1wb3J0YW5jaWEgZGUgdW5hIGNhcmFjdGVyw61zdGljYSBiYXPDoW5kb3NlIGVuIGxhIGRpc21pbnVjacOzbiBkZSBsYSBpbXB1cmV6YSBkZWwgbm9kbyAocG9yIGVqZW1wbG8sIEdpbmkgbyBlbnRyb3DDrWEpIGN1YW5kbyB1bmEgY2FyYWN0ZXLDrXN0aWNhIHNlIHV0aWxpemEgcGFyYSBkaXZpZGlyIGVuIGxvcyDDoXJib2xlcyBkZSBkZWNpc2nDs24uIEN1YW50byAqKm1heW9yIHNlYSBsYSBkaXNtaW51Y2nDs24gZGUgbGEgaW1wdXJlemEsIG3DoXMgaW1wb3J0YW50ZSBzZSBjb25zaWRlcmEgbGEgY2FyYWN0ZXLDrXN0aWNhKiouCgpgYGB7cn0KCnJmX21vZGVsMSR2YXJpYWJsZS5pbXBvcnRhbmNlCmRhdGEuZnJhbWUoaW1wdXJpdHk9cmZfbW9kZWwxJHZhcmlhYmxlLmltcG9ydGFuY2UpICU+JSBhcnJhbmdlKGRlc2MoaW1wdXJpdHkpKQoKYGBgCgpgYGB7cn0KcmZfbW9kZWwxIDwtIHJhbmdlcih0bWcgfiBwZWNfMjggKyBwZWNfMjcgKyBwZWNfMzIgKyBwZWNfNjIgKyBwc2QxMl81OCArIHBzZDEyXzUwICsgcHNkMTFfNTggKyBwZWNfNTEgKyBwc2QxMV81MCArIHBlY18yOSxkYXRhPXRyYWluLCBpbXBvcnRhbmNlID0gImltcHVyaXR5IikKcmZfbW9kZWwxCmBgYAoKIyMjIFByZWRpY2Npb24KCmBgYHtyfQpwcmVkaWN0aW9uczEgPC0gcHJlZGljdChyZl9tb2RlbDEsZGF0YSA9IHRlc3QsIHR5cGU9J3Jlc3BvbnNlJykKcHJlZGljdGlvbnMxJHByZWRpY3Rpb25zICU+JSBhcy5kYXRhLmZyYW1lCmBgYAoKIyMjIyBFcnJvciBNU0UgZW4gdGVzdAoKYGBge3J9Cm1zZTwtZnVuY3Rpb24oYWN0LHByZWQpIHttZWFuKChhY3QtIHByZWQpXjIpfQoKZGF0YS5mcmFtZShwcmVkPXByZWRpY3Rpb25zMSRwcmVkaWN0aW9ucywgYWN0PXRlc3QkdG1nKSAlPiUgc3VtbWFyaXNlKG1zZT1tc2UoYWN0LHByZWQpKQpgYGAKCiMjIyMgSW50ZXJ2YWxvcyBkZSBwcmVkaWNjaW9uIChxdWFudGlsZSByZWdyZXNzaW9uKQoKRW4gdmV6IGRlIHV0aWxpemFyIGVsIHByb21lZGlvIHNlIHV0aWx6YW4gbG9zIGN1YW50aWxlcyBwYXJhIHRlbmVyIHVuIGludGVydmFsbyBkZSBwcmVkaWNjacOzbiAoTWVpbnNoYXVzZW4sIDIwMDYpLiBBIGxhIGhvcmEgZGUgcmVhbGl6YXIgZWwgc3BsaXQsIGVuIHZleiBkZSB1dGlsaXphciBNU0UgbyBhbGd1bmEgb3RyYSBtZXRyaWNhIGRlIGltcHVyZXphLCBzZSB1dGlsaXphIHVuYSBtZXRyaWNhIHF1ZSB0aWVuZSBlbiBjdWVudGEgYSBsb3MgY3VhbnRpbGVzIC4gTHVlZ28gZW4gY2FkYSBob2phIGVuIHZleiBkZSBjYWxjdWxhciBlbCBwcm9tZWRpbywgc2UgY2FsY3VsYW4gY3VhbnRpbGVzLgoKYGBge3J9CnJmX21vZGVsMSA8LSByYW5nZXIodG1nIH4gLiAsZGF0YT10cmFpbiwgaW1wb3J0YW5jZSA9ICJpbXB1cml0eSIscXVhbnRyZWcgPSBUUlVFKQpyZl9tb2RlbDEKYGBgCgpgYGB7cn0KcHJlZGljdGlvbnMxIDwtIHByZWRpY3QocmZfbW9kZWwxLGRhdGEgPSB0ZXN0LCB0eXBlPSAicXVhbnRpbGVzIikKcHJlZGljdGlvbnMxJHByZWRpY3Rpb25zICU+JSBhcy5kYXRhLmZyYW1lKCkKCgpgYGAKCmBgYHtyfQpwMTwtZGF0YS5mcmFtZShwcmVkaWN0aW9uczEkcHJlZGljdGlvbnMsYWN0PXRlc3QkdG1nLGxhYmVsPSJNYWcgcHJlZGljdGlvbiIpCgpwMSAgJT4lIGdncGxvdCgpKwogIGdlb21fcG9pbnQoYWVzKHg9YWN0LHk9cXVhbnRpbGUuLjAuNSksY29sb3I9J3JlZCcpKwogIGdlb21fZXJyb3JiYXIoYWVzKHg9YWN0LHk9cXVhbnRpbGUuLjAuNSx5bWF4PXF1YW50aWxlLi4wLjkseW1pbj1xdWFudGlsZS4uMC4xKSxjb2xvcj0nb3JhbmdlJykrCiAgdGhlbWVfY2xhc3NpYygpCmBgYAoKIyMjIyBJbnRlcnZhbG8gZGUgY29uZmlhbnphCgpTaW1pbGFyIGFsIGludGVydmFsbyBkZSBwcmVkaWNjaW9uLiBFbiBsYXMgaW1wbGVtZW50YWNpb25lcyBkZSBSYW5kb20gRm9yZXN0cywgc3VlbGUgY29uZnVkaXJzZS4gRW4gdGVybWlub3MgZ2VuZXJhbGVzLCB1bm8gc2UgYXBsaWNhIHNvYnJlIHVuYSBvYnNlcnZhY2lvbi9wcmVkaWNjacOzbiBlbiBnZW5lcmFsLCBtaWVudHJhcyBxdWUgZWwgb3RybyB0cmF0YSBzb2JyZSBlc3RhZGlzdGljb3MuIFVuIGVqZW1wbG8gc2VyaWEsIGxhIGRpZmVyZW5jaWEgZW50cmUgZGVzdmlhY2lvbiBlc3RhbmRhciBkZSB1bmEgdmFyaWFibGUgeSBlbCBlcnJvciBlc3RhbmRhciBzb2JyZSB1biBjb25qdW50byBkZSBtdWVzdHJhcy4KCklmIHdlIGNoYW5nZSB0aGUgdHJhaW5pbmcgZGF0YXNldCBqdXN0IGEgbGl0dGxlIGJpdCwgd2lsbCBSYW5kb20gRm9yZXN0IGdpdmUgeW91IHRoZSBzYW1lIHJlc3VsdCBmb3IgdGhhdCBwYXJ0aWN1bGFyIGV4YW1wbGU/CgooV2FnbmVyIGV0IGFsLiAyMDE0KSBCYXNhZG8gZW4gdW5hIHRlY25pY2EgcXVlIHNlIGxsYW1hIGphY2tuaWZlLgoKYGBge3J9CnJmX21vZGVsMSA8LSByYW5nZXIodG1nIH4gLiwgZGF0YT10cmFpbiwgaW1wb3J0YW5jZSA9ICJpbXB1cml0eSIsa2VlcC5pbmJhZyAgPSBUUlVFKQpyZl9tb2RlbDEKYGBgCgpgYGB7cn0KcHJlZGljdGlvbnMxIDwtIHByZWRpY3QocmZfbW9kZWwxLGRhdGEgPSB0cmFpbiwgdHlwZT0gInNlIikKCnByZWRpY3Rpb25zMSRwcmVkaWN0aW9ucyAlPiUgYXMuZGF0YS5mcmFtZSgpCnByZWRpY3Rpb25zMSRzZSAlPiUgYXMuZGF0YS5mcmFtZSgpCgpkYXRhLmZyYW1lKHByZWQ9cHJlZGljdGlvbnMxJHByZWRpY3Rpb25zLCBzZT0gcHJlZGljdGlvbnMxJHNlLCBhY3Q9dHJhaW4kdG1nKSAlPiUgZ2dwbG90KCkrCiAgZ2VvbV9wb2ludChhZXMoeD1hY3QseT1wcmVkKSxjb2xvcj0ncmVkJykrCiAgZ2VvbV9lcnJvcmJhcihhZXMoeD1hY3QseT1wcmVkLHltYXg9cHJlZCtzZSx5bWluPXByZWQtc2UpLGNvbG9yPSdvcmFuZ2UnKSsKICB0aGVtZV9jbGFzc2ljKCkKYGBgCg==